/*
This file is part of asproxy.

asproxy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

asproxy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with asproxy.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "proxydaemon.h"
#include "proxyhelper.h"
#include "log.h"
#include "proxythread.h"
#include <dirent.h>
#include <signal.h>
#include <arpa/inet.h>
#include <dlfcn.h>

int start_daemon(int length, PROXYCONFIG *proxyconfig)
{
	// Get the details into the global member for later use.
	strcpy(g_proxyconfig.DestinationIP, proxyconfig->DestinationIP);
	strcpy(g_proxyconfig.ListeningIP, proxyconfig->ListeningIP);
	strcpy(g_proxyconfig.FailoverIP, proxyconfig->FailoverIP);
	g_proxyconfig.DestinationPort = proxyconfig->DestinationPort;
	g_proxyconfig.ListeningPort = proxyconfig->ListeningPort;
	g_proxyconfig.BufferSize = proxyconfig->BufferSize;
	g_proxyconfig.SocketTimeout = proxyconfig->SocketTimeout;
	g_proxyconfig.ConnectionBacklog = proxyconfig->ConnectionBacklog;
	g_proxyconfig.WorkerThreadCount = proxyconfig->WorkerThreadCount;
	strcpy(g_proxyconfig.SourceDetailFormat, proxyconfig->SourceDetailFormat);
	strcpy(g_proxyconfig.SSLCertFile, proxyconfig->SSLCertFile);
	strcpy(g_proxyconfig.SSLCAPath, proxyconfig->SSLCAPath);
	strcpy(g_proxyconfig.CustomHandler, proxyconfig->CustomHandler);
	g_proxyconfig.Debug = proxyconfig->Debug;
	
	// Initialize the ssl context
	g_psslcontext = NULL;
	
	// Fork the Parent Process
	pid_t pid = fork();
	if (pid < 0)
	{
		char buf[100] = {0};
		sprintf(buf, "Could not fork the daemon. Returned PID: %d.", pid);
		log_error(buf);
		
		return -1;
	}
	else if (pid > 0) 
	{		
		printf("Daemon Started, PID: %d, Listening Port: %d.\n", pid, proxyconfig->ListeningPort);
		return 0;
	}
	
	/****** CHILD PROCESS ******/
	
	log_open();
		
	// Permissions setter
	umask(0);
	
	// Create the new SID for the child process
	pid_t sid = setsid();
	if (sid < 0)
	{
		char buf[100] = {0};
		sprintf(buf, "Could not create SID for the child daemon. Returned SID: %d", sid);
		log_error(buf);
		
		return -1;
	}
	
	// Change the current working directory
	if (chdir("/") < 0)
	{
		log_error("Could not change Daemon's working directory.");		
		return -1;
	}
	
	// Close out the file descriptors
	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);
	
	// log that the process has been started
	char buf[100] = {0};
	sprintf(buf, "Daemon started, SID: %d listening to port: %d", sid, g_proxyconfig.ListeningPort);
	log_info(buf);
	
	// Initialize the context
	if (strcmp(g_proxyconfig.SSLCertFile, NONE_DEFAULT) != 0)
	{
		initserverctx(); // This will ensure that it was loaded once.
		init_locks(); // Initiate locking scheme for multithreaded ssl server
	}
	
	// Main Daemon Loop
	run_listener();
	
	// Free the Context if it exists
	if (g_psslcontext != NULL)
	{
		kill_locks();
		SSL_CTX_free(g_psslcontext);		
	}
	
	log_close();
					
	return -1;
};

int stop_daemons()
{
	int nResult = 0;

	struct dirent* childdir = NULL;
	DIR* procdir = opendir("/proc");
	if (procdir != NULL)
	{
		while ((childdir = readdir(procdir)) != NULL)
		{
			if(strcmp(childdir->d_name, ".") == 0 || strcmp(childdir->d_name, "..") == 0)
				continue;
				
			char statuspath[50] = {0};
			sprintf(statuspath, "/proc/%s/status", childdir->d_name);
			
			// Read the Child Path and check if the name is asproxy
			FILE* fp = NULL;
			char buffer[1024] = {0};
			
			fp = fopen(statuspath, "r");
			
			if (fp == NULL)
				continue; 
				
			size_t bytes_read = fread (buffer, 1, sizeof (buffer), fp);			
			fclose(fp);
			if (bytes_read == 0 || bytes_read == sizeof (buffer))
				continue;
			
			/* match to see if this is an asproxy process. */
			char* match = strstr (buffer, "asproxy");
			if (match == NULL)
				continue;
				
			// If you have an asproxy Match, make sure that the proxy you are 
			// looking at is the one with parent id of 1
			int ppid;
			char* ppidmatch = strstr(buffer, "PPid");
			if (ppidmatch == NULL)
				continue;
			sscanf(ppidmatch, "PPid:%d", &ppid);			
			if (ppid != 1)
				continue;
			
			// Found a good match. Kill the Process, and print
			int pid = atoi(childdir->d_name);
			if (pid > 0)
			{
				kill(pid, SIGKILL);
				printf("asproxy PID[%s] Killed\n", childdir->d_name);
			}
		}
	}
	
	closedir(procdir);
	
	return nResult;
};

int show_daemoninfo()
{
	int nResult = 0;

	struct dirent* childdir = NULL;
	DIR* procdir = opendir("/proc");
	if (procdir != NULL)
	{
		while ((childdir = readdir(procdir)) != NULL)
		{
			if(strcmp(childdir->d_name, ".") == 0 || strcmp(childdir->d_name, "..") == 0)
				continue;
				
			char statuspath[50] = {0};
			sprintf(statuspath, "/proc/%s/status", childdir->d_name);
			
			// Read the Child Path and check if the name is asproxy
			FILE *fp = NULL;
			char buffer[1024];
			
			memset(buffer, 0, sizeof(buffer));
			fp = fopen(statuspath, "r");
			
			if (fp == NULL)
				continue; 
				
			size_t bytes_read = fread (buffer, 1, sizeof (buffer), fp);			
			fclose(fp);
			if (bytes_read == 0 || bytes_read == sizeof (buffer))
				continue;
			
			/* match to see if this is an asproxy process. */
			char* match = strstr (buffer, "asproxy");
			if (match == NULL)
				continue;
				
			// If you have an asproxy Match, make sure that the proxy you are looking at 
			// is the one with parent id of 1
			int ppid;
			char* ppidmatch = strstr(buffer, "PPid");
			if (ppidmatch == NULL)
				continue;
			sscanf(ppidmatch, "PPid:%d", &ppid);			
			if (ppid != 1)
				continue;
			
			// Found a good match. Print the status
			printf("\nasproxy Status:\n%s\n\n", buffer);
		}
	}
	
	closedir(procdir);
	
	return nResult;
}

void run_listener()
{
	errno = 0; // Initialize Errno
	int sockfd;
	socklen_t clilen;
	struct sockaddr_in serv_addr, cli_addr;
	pthread_mutex_init(&g_mutex, NULL);
	pthread_mutex_init(&g_sslmutex, NULL);
	g_workerthreadcount = 0;
	
	// Set the Shared object handlers so that we load it up only once. 
	if (strcmp(g_proxyconfig.CustomHandler, NONE_DEFAULT) != 0)
	{
		void* plibhandle = NULL;
		plibhandle = dlopen(g_proxyconfig.CustomHandler, RTLD_NOW);
		if (plibhandle == NULL)
			log_error("Could not load the Custom Handler.");
		else 
		{
			g_phandle_incoming = (char* (*)(char*, int*))dlsym(plibhandle, "handleincoming");
			g_phandle_outgoing = (char* (*)(char*, int*))dlsym(plibhandle, "handleoutgoing");
		}
	}
	
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0) 
	{
		log_error("Error opening socket");
		
		if (errno > 0)
			log_error(strerror(errno));
		return;
	}

	bzero((char *) &serv_addr, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;

	if (strcmp(g_proxyconfig.ListeningIP, ANY_DEFAULT) == 0)
		serv_addr.sin_addr.s_addr = INADDR_ANY;
	else 
		inet_pton(AF_INET, g_proxyconfig.ListeningIP, &(serv_addr.sin_addr));
		
	serv_addr.sin_port = htons(g_proxyconfig.ListeningPort);

	if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) 
	{
		log_error("Error on socket binding");
		if (errno > 0)
			log_error(strerror(errno));
		goto END_PROCESS;
	}
	
	while (1)
	{		
		listen(sockfd, g_proxyconfig.ConnectionBacklog); // blocking

		clilen = sizeof(cli_addr);
		int acceptfd = accept(sockfd, 
				 (struct sockaddr *) &cli_addr, 
				 &clilen);
	
		if (acceptfd <= 0) 
		{
			log_error("Error on socket accept");
			
			if (errno > 0)
				log_error(strerror(errno));
			continue;
		}
		
		if (validateclientcount() < 0)
		{
			close(acceptfd);
			continue;
		}
			
		// Create the client thread (but make sure you set it to detached mode so that the resouce 
		// allocated can be retrieved after its termination
		thread_parm_t *parm = NULL;
		parm = malloc(sizeof(thread_parm_t));
		parm->value = acceptfd;
		memset(parm->string, 0, 128);
		sprintf(parm->string, "Client - %s:%d", (char *)inet_ntoa(cli_addr.sin_addr), cli_addr.sin_port);

		pthread_t clientthreadid;
		pthread_attr_t attr;
		pthread_attr_init(&attr);
		pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // need not do a join on this thread
				
		int clierr = pthread_create(&clientthreadid, &attr, &processclient, (void *)parm);		
		if (clierr != 0)
		{			
			close(acceptfd);
			continue;
		}
	}

END_PROCESS:	
	if (errno > 0)
		log_error(strerror(errno));
	close(sockfd);	
						
	pthread_mutex_destroy(&g_mutex);
	pthread_mutex_destroy(&g_sslmutex);
}


